package il.technion.cs236369.proxy;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHeader;

/**
 * The managing envelope of the SQL database 
 *
 */
public class CacheManager
{
	/**
	 * Contains the HTTP message's data structure in a cache appropriate manner
	 *
	 */
	public class Entry 
	{
		public String m_url;
		public String m_headers;
		public byte[] m_body;
		public String m_entityTag;
		
		public Entry(String url, Header[] headers, byte[] body)
		{
			m_url = url;
			StringBuilder headersBuilder = new StringBuilder();
			String entityTag = null;
			
			// read the headers individually
			for (Header header : headers) 
			{
				// save the entity tag especially
				if (entityTag == null && header.getName().equals(HttpHeaders.ETAG) )
				{
					entityTag = header.getValue();
				}
				headersBuilder.append(header.getName()).append(":").append(header.getValue()).append("/n");
			}
			
			m_headers = headersBuilder.toString();
			m_body = body;
			m_entityTag = entityTag;
		}
		
		public Entry(String url, String headers, byte[] body, String entityTag)
		{
			m_url = url;
			m_headers = headers;
			m_body = body;
			m_entityTag = entityTag;
		}

	}

	private final String m_tableName;
	private final String[] m_dbColumns = { "url", "headers", "body", "entitytag" };
	
	private Connection dbConn;
	private Entry lastEntry; 
	
	public CacheManager(String driver, String dbUrl, 
				 		String dbName, String tableName, 
				 		String username, String password) 
				 		throws ClassNotFoundException, SQLException
	{
		dbConn = DriverManager.getConnection(dbUrl + dbName, username, password);
		m_tableName = tableName;
	}

	/**
	 * Creates a CacheEntry object from the cache
	 * @param url - the url idetifying the cache row
	 * @return a CacheEntry object containing all the info from the cache row,
	 * 		null if no row or error
	 */
	private Entry RetrieveRow(String url) 
	{
		PreparedStatement statement;

		// prepare a statement for selecting all rows with the given url
		try 
		{
			statement = dbConn.prepareStatement("select * from " + m_tableName + " where " + m_dbColumns[0] + "=?");
		} catch (SQLException e) 
		{
			e.printStackTrace();
			return null;
		}
		
		ResultSet result = null;
		
		try 
		{
			// patch the url into the statement
			statement.setString(1, url);
			// and run it
			result = statement.executeQuery();
			
			if (result.next() == false)
			{
				return null;
			}
			
			// and return the entry if there
			return new Entry(
					result.getString(m_dbColumns[0]),
					result.getString(m_dbColumns[1]),
					result.getBytes(m_dbColumns[2]),
					result.getString(m_dbColumns[3]));
		} catch (SQLException e) 
		{
			e.printStackTrace();
			return null;
		}
		finally
		{
			try
			{
				statement.close();
			}
			catch (Exception e) 
			{
				e.printStackTrace();
			}
			
			try 
			{
				if (result != null) result.close();
			} catch (Exception e) 
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * Updates the cache with info from the CacheEntry object
	 * @param entry - the CacheEntry object containing the request info
	 */
	private void UpdateRow(Entry entry)
	{
		PreparedStatement statement = null;
		
		try
		{
			statement = dbConn.prepareStatement("update " + m_tableName + " set " +
												m_dbColumns[1] + "=?, " +
												m_dbColumns[2] + "=?, " + 
												m_dbColumns[3] + "=? " + 
												"where " + m_dbColumns[0] + "=?");
		} catch (SQLException e)
		{
			e.printStackTrace();
			return;
		}
		
		try
		{
			statement.setString(1, entry.m_headers);
			statement.setBytes(2, entry.m_body);
			statement.setString(3, entry.m_entityTag);
			statement.setString(4, entry.m_url);
			statement.execute();
		} catch (SQLException e) 
		{
			e.printStackTrace();
		}
		finally
		{
			try 
			{ 
				if (statement != null) statement.close();
			} catch (SQLException e) 
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * Insert a new row into the cache
	 * @param entry - the CacheEntry object containing the info to fill
	 * the row with
	 */
	public void InsertRow(Entry entry) 
	{
		// if already in the cache, send an update
		if (RetrieveRow(entry.m_url) != null)
		{
			UpdateRow(entry);
			return;
		}
		
		PreparedStatement statement = null;
		
		try
		{
			statement = dbConn.prepareStatement("INSERT INTO " + m_tableName + " VALUES(?, ?, ?, ?)");
		} catch (SQLException e)
		{
			e.printStackTrace();
			return;
		}
		
		try
		{
			statement.setString(1, entry.m_url);
			statement.setString(2, entry.m_headers);
			statement.setBytes(3, entry.m_body);
			statement.setString(4, entry.m_entityTag);
			statement.execute();
			
		} 
		catch (SQLException e) 
		{
			e.printStackTrace();
		}
		finally
		{
			try 
			{ 
				if (statement != null) statement.close();
			} 
			catch (SQLException e) 
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * Deletes a row in the cache
	 * @param url - the url identifier for the row to be deleted
	 */
	public void DeleteRow(HttpRequest request) 
	{
		PreparedStatement statement = null;
		
		try
		{
			statement = dbConn.prepareStatement("DELETE FROM " + m_tableName + " WHERE " + m_dbColumns[0] + "=?");
		}
		catch (SQLException e)
		{
			e.printStackTrace();
			return;
		}
		
		try
		{
			statement.setString(1, request.getRequestLine().getUri());
			statement.execute();
			
		} 
		catch (SQLException e) 
		{
			e.printStackTrace();
		}
		finally
		{
			try 
			{ 
				if (statement != null) statement.close();
			}
			catch (SQLException e)
			{
				e.printStackTrace();
			}
		}
		
	}

	/**
	 * Check the cache for a cache response to the given request
	 * Will save the response for future access
	 * 
	 * @param request the request to check
	 * @return true if response is cached, false otherwise
	 */
	public boolean ResponseCached(HttpRequest request)
	{
		lastEntry = RetrieveRow(request.getRequestLine().getUri());
		return !(lastEntry == null);
	}

	/**
	 * Returns the last cache entry's entity tag
	 * @return the entity tag, null if no last entry
	 */
	public String GetLatestEntityTag()
	{
		if (lastEntry == null)
		{
			return null;
		}
		return lastEntry.m_entityTag;
	}

	/**
	 * Write the latest cache entry into an HttpResponse
	 * @param response - the contents of the CacheEntry in HttpResponse form
	 */
	public void WriteCachedResponseBody(HttpResponse response)
	{
		String[] headersSplit = lastEntry.m_headers.split("\n");
		
		for (String header : headersSplit) 
		{
			String[] headerSplit = header.split(":");
			response.setHeader(new BasicHeader(headerSplit[0], headerSplit[1]));
		}
		
		response.setHeader(new BasicHeader(HttpHeaders.ETAG, lastEntry.m_entityTag));
		response.setEntity(new ByteArrayEntity(lastEntry.m_body));
	}

	public Entry CreateEntry(String url, Header[] headers, byte[] body)
	{
		return new Entry(url, headers, body);
	}
	
	public Entry CreateEntry(String url, String headers, 
							 byte[] body, String entityTag)
	{
		return new Entry(url, headers, body, entityTag);
	}
}
